﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;

namespace Model.DocumentDB
{
    /// <summary>Represents a list that can be loaded.</summary>
    public class LoadableCollection<T> : ObservableCollection<T>, IRefreshable
    {
        private readonly Func<Task<IEnumerable<T>>> _loader;
        private Task<IEnumerable<T>> _refreshTask;

        /// <summary>Returns a pre-loaded empty list.</summary>
        /// <returns>Empty collection</returns>
        public static LoadableCollection<T> CreateEmpty()
        {
            Func<Task<IEnumerable<T>>> loader = () => Task.FromResult(new T[0] as IEnumerable<T>);
            var collection = new LoadableCollection<T>(loader);

            collection.RefreshAsync().Wait();

            return collection;
        }

        /// <summary>Constructor taking a loader in parameter.</summary>
        /// <param name="loader">Function asynchronously loading the collection.</param>
        public LoadableCollection(Func<Task<IEnumerable<T>>> loader)
        {
            if (loader == null)
            {
                throw new ArgumentNullException("loader");
            }
            _loader = loader;
        }

        /// <summary><c>true</c> iif the collection is currently loading.</summary>
        public bool IsLoading
        {
            get
            {
                return _refreshTask != null
                    && !_refreshTask.IsCanceled
                    && !_refreshTask.IsCompleted
                    && !_refreshTask.IsFaulted;
            }
        }

        /// <summary><c>true</c> iif the collection has successfully loaded.</summary>
        public bool IsLoaded
        {
            get { return _refreshTask != null && _refreshTask.IsCompleted && !_refreshTask.IsFaulted; }
        }

        /// <summary><c>true</c> iif the collection has faulted during load.</summary>
        public bool HasError
        {
            get { return _refreshTask != null && (_refreshTask.IsFaulted || _refreshTask.IsCanceled); }
        }

        /// <summary>Clear the collection after an error.</summary>
        public void ClearError()
        {
            if (!HasError)
            {
                throw new InvalidOperationException("Isn't in a HasError state");
            }
            _refreshTask = Task.FromResult(new T[0] as IEnumerable<T>);
            Clear();
            OnIsLoadingChanged();
        }

        /// <summary>Refreshes the collection asynchronously.</summary>
        /// <returns>Asynchronous completion.</returns>
        public async Task RefreshAsync()
        {
            try
            {
                if (IsLoading)
                {
                    await _refreshTask;
                }
                else
                {
                    _refreshTask = _loader();
                    OnIsLoadingChanged();

                    var list = await _refreshTask;

                    Clear();
                    foreach (var i in list)
                    {
                        Add(i);
                    }
                }
            }
            finally
            {
                OnIsLoadingChanged();
            }
        }

        /// <summary>Refreshes the collection asynchronously if it isn't loaded (or faulted) yet.</summary>
        /// <returns>Asynchronous completion.</returns>
        public async Task EnsureLoadedAsync()
        {
            if (!IsLoaded)
            {
                await RefreshAsync();
            }
        }

        private void OnIsLoadingChanged()
        {
            OnPropertyChanged(new PropertyChangedEventArgs("IsLoading"));
            OnPropertyChanged(new PropertyChangedEventArgs("IsLoaded"));
            OnPropertyChanged(new PropertyChangedEventArgs("HasError"));
        }
    }
}